package me.yricky.alia.utils.gds

import me.yricky.alia.data.base.Coord
import me.yricky.alia.data.hierarchy.*
import me.yricky.alia.data.shape.Shape
import me.yricky.alia.data.shape.Text
import java.io.InputStream

/**
 * @author Yricky
 * @date 2022/1/30
 */
fun convert2Lib(inputStream: InputStream):Library{
    return convert2Lib(GDS2Iterator(inputStream))
}

fun convert2Lib(records:List<GDS.Record.Raw>): Library {
    return convert2Lib(records.iterator())
}

fun convert2Lib(iterator: Iterator<GDS.Record.Raw>): Library{
    var name = ""
    var unitUser = 1.0
    var unitMeter = 1.0
    val cells:MutableList<Cell> = ArrayList()
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDLIB){
            break
        }
        when(rec){
            is GDS.Record.StringRecord ->{
                if(rec.type == GDS.RecordType.LIBNAME){
                    name = rec.name
                }
            }
            is GDS.Record.Units ->{
                unitMeter = rec.unitInMeter
                unitUser = rec.unitInUserUnit
            }
            is GDS.Record.TimeRecord -> {
                if(rec.type == GDS.RecordType.BGNSTR){
                    cells.add(convert2Cell(iterator))
                }
            }
            else->{}
        }
    }
    return Library(
        name, unitMeter, unitUser, cells
    )
}



private fun convert2Cell(iterator: Iterator<GDS.Record.Raw>): Cell {
    var name = ""
    val layers:MutableMap<Layer.LDPair,MutableList<Shape>> = HashMap()
    val cellRefs = ArrayList<CellRef>()
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDSTR){
            break
        }
        when(rec){
            is GDS.Record.StringRecord ->{
                if(rec.type == GDS.RecordType.STRNAME){
                    name = rec.name
                }
            }
            is GDS.Record.LabelRecord ->{
                when(rec.type){
                    GDS.RecordType.TEXT,GDS.RecordType.BOUNDARY -> {
                        val layerShape = if(rec.type == GDS.RecordType.TEXT){
                            convertText(iterator)
                        } else {
                            convertBoundary(iterator)
                        }
                        layerShape.second?.let{ shape->
                            (layers[layerShape.first] ?: ArrayList<Shape>().also {
                                layers[layerShape.first] = it
                            }).add(shape)
                        }
                    }
                    GDS.RecordType.SREF -> {
                        convert2CellRef(iterator).let{
                            cellRefs.add(it)
                        }
                    }
                    GDS.RecordType.AREF ->{
                        cellRefs.add(convert2ArrayRef(iterator))
                    }
                    else -> {}
                }

            }
            else->{}
        }
    }
    return Cell(name,layers.map {
        Layer(it.key,it.value)
    },cellRefs)
}

private fun convert2CellRef(iterator: Iterator<GDS.Record.Raw>):SingleCellRef{
    var name = ""
    var off = Coord(0,0)
    var mag = 1.0
    var angle = 0.0
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDEL){
            break
        }
        when(rec){
            is GDS.Record.StringRecord -> if(rec.type == GDS.RecordType.SNAME)name = rec.name
            is GDS.Record.XY -> off = rec.shape.vertices.first()
            is GDS.Record.Mag -> mag = rec.double
            is GDS.Record.Angle -> angle = rec.double
            else ->{}
        }
    }
    return SingleCellRef(
        name, off, mag, angle
    )
}

private fun convert2ArrayRef(iterator: Iterator<GDS.Record.Raw>):ArrayCellRef{
    var name = ""
    var off = Coord(0,0)
    var mag = 1.0
    var angle = 0.0
    var col:Short = 1
    var row:Short = 1
    var coordX = Coord(0,0)
    var coordY = Coord(0,0)
    GDS.Record.parse(iterator.next()).let{
        if(it is GDS.Record.StringRecord && it.type == GDS.RecordType.SNAME){
            name = it.name
        }
    }
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDEL){
            break
        }
        when(rec){
            is GDS.Record.ColRow ->{
                col = rec.col
                row = rec.row
            }
            is GDS.Record.XY -> {
                if(rec.shape.vertices.size == 3){
                    off = rec.shape.vertices.first()
                    coordX = rec.shape.vertices[1]
                    coordY = rec.shape.vertices[2]
                }
            }
            is GDS.Record.Mag -> mag = rec.double
            is GDS.Record.Angle -> angle = rec.double
            else ->{}
        }
    }
    return ArrayCellRef(
        name, off, col, row, coordX, coordY, mag, angle
    )
}

private fun convertBoundary(iterator: Iterator<GDS.Record.Raw>):Pair<Layer.LDPair, Shape?>{
    var layer = 0
    var dataType = 0
    var shape: Shape? = null
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDEL){
            break
        }
        when(rec){
            is GDS.Record.IdRecord ->{
                if(rec.type == GDS.RecordType.LAYER){
                    layer = rec.id.toInt()
                }else if(rec.type == GDS.RecordType.DATATYPE){
                    dataType = rec.id.toInt()
                }
            }
            is GDS.Record.XY ->{
                shape = rec.shape
            }
            else->{}
        }
    }
    return Pair(Layer.LDPair(layer,dataType),shape)
}

private fun convertText(iterator: Iterator<GDS.Record.Raw>):Pair<Layer.LDPair, Text?>{
    var layer = 0
    var textType = 0
    var coord: Coord? = null
    var presentation: GDS.Record.Presentation? = null
    var text = ""
    while (iterator.hasNext()){
        val rec = GDS.Record.parse(iterator.next())
        if(rec.type == GDS.RecordType.ENDEL){
            break
        }
        when(rec){
            is GDS.Record.IdRecord ->{
                if(rec.type == GDS.RecordType.LAYER){
                    layer = rec.id.toInt()
                }else if(rec.type == GDS.RecordType.TEXTTYPE){
                    textType = rec.id.toInt()
                }
            }
            is GDS.Record.XY ->{
                coord = rec.shape.vertices.getOrNull(0)
            }
            is GDS.Record.Presentation ->{
                presentation = rec
            }
            is GDS.Record.StringRecord ->{
                if(rec.type == GDS.RecordType.STRING){
                    text = rec.name
                }
            }
            else->{}
        }
    }
    return Pair(Layer.LDPair(layer,textType), coord?.let{
        Text(
            presentation?.getFont() ?: GDS.Record.Presentation.Font.DefaultFont,
            presentation?.getHAlign() ?: GDS.Record.Presentation.Align.No,
            presentation?.getVAlign() ?: GDS.Record.Presentation.Align.No,
            it,
            text
        )
    }
    )
}